
//
// Copyright 2009 Arne Halvorsen (arne.halvorsen@gmail.com)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Project page: http://code.google.com/p/mf4j/

//
// 091116 - aha42 - Starts in line change tracking.
// 091121 - aha42 - Added project page link (google code) to header comment.
// 091210 - aha42 - IOException -> FlickrException.
//

package org.aha.mf4j.reflection;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

import org.aha.mf4j.FlickrException;
import org.aha.mf4j.Permissions;
import org.aha.mf4j.response.FlickrObject;

/**
 * <p>
 *   Represents a Flickr method.
 * </p>
 * @author Arne Halvorsen (aha42)
 */
public final class FlickrMethod 
{
  private final String m_name;
  
  private final boolean m_needsLogin;
  
  private final boolean m_needsSigning;
  
  private final int m_requiredPerms;
  
  private final String m_desc;
  
  private final String m_response;
  
  private final String m_explanation;
  
  private final SortedMap<String, FlickrArgument> m_args=
    new TreeMap<String, FlickrArgument>();
  
  private final List<String> m_mandatoryArgs=new ArrayList<String>();
  
  private FlickrError[] m_errors=FlickrError.E_ARRAY;
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param rspFo {@link FlickrObject} of type {@code rsp}, parsed response 
   *              for method info request. 
   * @throws FlickrException If fails.
   */
  public FlickrMethod(FlickrObject rspFo) throws FlickrException
  {
    if (rspFo==null)
    {
      throw new NullPointerException("rspFo");
    }
    
    rspFo.assertType("rsp");
    
    FlickrObject methodFo=rspFo.getChild("method");
    FlickrObject argumentsFo=rspFo.getChild("arguments");
    FlickrObject errorsFo=rspFo.getChild("errors");
    
    m_name=methodFo.getMandatoryAttribute("name");
    
    String needsLogin=methodFo.getMandatoryAttribute("needslogin");
    m_needsLogin=(parseAsInt("needslogin", needsLogin, 1)==1);
    
    String needsSigning=methodFo.getMandatoryAttribute("needssigning");
    m_needsSigning=(parseAsInt("needssigning", needsSigning, 1)==1);
    
    String requiredPerms=methodFo.getMandatoryAttribute("requiredperms");
    m_requiredPerms=parseAsInt("requiredperms", requiredPerms, 3);
    
    FlickrObject fo=methodFo.getChild("description");
    m_desc=(fo==null) ? null : fo.getValue();
    
    fo=methodFo.getChild("response");
    m_response=(fo==null) ? null : fo.getValue();
    
    fo=methodFo.getChild("explanation");
    m_explanation=(fo==null) ? null : fo.getValue();
    
    if (argumentsFo!=null)
    {
      FlickrObject[] argumentFos=argumentsFo.getChildren("argument");
      int n=(argumentFos==null) ? 0 : argumentFos.length;
      if (n>0)
      {
        for (int i=0; i<n; i++)
        { 
          FlickrArgument fa=new FlickrArgument(argumentFos[i]);
          String name=fa.getName();
          m_args.put(name, fa);
          if (!fa.getIsOptional()) m_mandatoryArgs.add(name);
        }
      }
    }
    if (errorsFo!=null)
    {
      FlickrObject[] errorFos=errorsFo.getChildren("error");
      int n=(errorFos==null) ? 0 : errorFos.length;
      if (n>0)
      {
        m_errors=new FlickrError[n];
        for (int i=0; i<n; i++)
        { 
          m_errors[i]=new FlickrError(errorFos[i]);
        }
      }
    }
  }
  
  /**
   * <p>
   *   Used by constructor to parse natural numbers.
   * </p>
   * @param name Name on parameter being parsed.
   * @param v    Parameter value.
   * @param max  Max parameter value.
   * @throws FlickrException If {@code v} can not be parsed as an {@code int} or
   *         parsed value {@code <0} or {@code >max}.
   */
  private static int parseAsInt(String name, String v, int max)
    throws FlickrException
  {
    try
    {
      int retVal=Integer.parseInt(v);
           if (retVal<0)   throw new FlickrException(name+"<0 : "+v);
      else if (retVal>max) throw new FlickrException(name+">"+max+" : "+v);
      
      return retVal;
    }
    catch (NumberFormatException nfx)
    {
      throw new FlickrException("failed to parse "+name+" as int : "+v);
    }
  }
  
  /**
   * <p>
   *   Gets method's name.
   * </p>
   * @return Name.
   */
  public String getName(){ return m_name; }
  
  /**
   * <p>
   *   Tells if method needs user to be logged in.
   * </p>
   * @return {@code true} if needs login, {@code false} if does not.
   */
  public boolean getNeedsLogin(){ return m_needsLogin; }
  
  /**
   * <p>
   *   Tells if method needs request to be signed.
   * </p>
   * @return {@code true} if needs login, {@code false} if does not.
   */
  public boolean getNeedsSigning(){ return m_needsSigning; }
  
  /**
   * <p>
   *   Gets method's required permission.
   * </p>
   * @return Permission required.
   */
  public int getRequiredPermission(){ return m_requiredPerms; }
  
  /**
   * <p>
   *   Tells if given permissions are sufficient for executing method.
   * </p>
   * @param p Permission.
   * @return {@code true} if {@code p} is sufficient, {@code false} if is not.
   * @throws IllegalArgumentException If {@code p} does not represents 
   *         permissions.
   */
  public boolean isRequiredPermission(String p)
  {
    if (p==null)
    {
      throw new NullPointerException("p");
    }
    
    return Permissions.hasPermission(m_requiredPerms, p);
  }
  
  /**
   * <p>
   *   Gets method's description.
   * </p>
   * @return Description or {@code null} if does not have.
   */
  public String getDescription(){ return m_desc; }
  
  /**
   * <p>
   *   Gets example response XML.
   * </p>
   * @return example response XML or {@code null} if does not have.
   */
  public String getResponse(){ return m_response; }
  
  /**
   * <p>
   *   Gets method's explanation.
   * </p>
   * @return Explanation or {@code null} if does have.
   */
  public String getExplanation(){ return m_explanation; }
  
  /**
   * <p>
   *   Gets meta data for named argument.
   * </p>
   * @param name Argument name.
   * @return {@link FlickrArgument} or {@code null} if method does not have 
   *         named argument.
   */
  public FlickrArgument getArgument(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    return m_args.get(name);
  }
  
  /**
   * <p>
   *   Tells if method has named argument.
   * </p>
   * @param name Argument name.
   * @return {@code true} if has, {@code false} if has not.
   */
  public boolean hasArgument(String name){ return getArgument(name)!=null; }

  /**
   * <p>
   *   Gets method's argument meta data.
   * </p>
   * @return {@link FlickrArgument}s.
   */
  public FlickrArgument[] getArguments()
  { 
    return m_args.values().toArray(FlickrArgument.E_ARRAY); 
  }
  
  /**
   * <p>
   *   Tells if method's argument is optional.
   * </p>
   * @param name Argument name.
   * @return {@code true} if is optional, {@code false} if is mandatory.
   */
  public boolean argOptional(String name)
  {
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    FlickrArgument fa=m_args.get(name);
    if (fa==null)
    {
      throw new IllegalArgumentException("no argument named "+name);
    }
    
    return fa.getIsOptional();
  }
  
  /**
   * <p>
   *   Gets number of mandatory arguments method has.
   * </p>
   * @return Count.
   */
  public int getMandatoryArgCount(){ return m_mandatoryArgs.size(); }
  
  /**
   * <p>
   *   Gets name on mandatory argument.
   * </p>
   * @param idx Index of mandatory mandatory argument.
   * @return Name.
   * @throws IndexOutOfBoundsException If 
   *         {@code idx<0 || idx>=getMandatoryArgCount()}
   */
  public String getMandatoryArgName(int idx){ return m_mandatoryArgs.get(idx); }
  
  /**
   * <p>
   *   Gets number of errors method has.
   * </p>
   * @return Count.
   */
  public int getErrorCount(){ return m_errors.length; }
  
  /**
   * <p>
   *   Gets
   *   {@link FlickrError} of {@code this} method.
   * </p>
   * @param idx Error's index.
   * @return {@code FlickrError}.
   * @throws IndexOutOfBoundsException If {@code idx<0 || idx>=getErrorCount()}.
   */
  public FlickrError getError(int idx){ return m_errors[idx]; }
  
  
  /**
   * <p>
   *   The empty array shared.
   * </p>
   */
  public static final FlickrMethod[] E_ARRAY=new FlickrMethod[0];
  
}
